MFA(Multi-Factor Authentication)是除了密碼以外,再加一道驗證,例如:
第一層 | 第二層 | 常見組合 |
---|---|---|
Email + Password | 簡訊驗證碼(SMS) | 最常見、安全性高 |
Google Login | Email 驗證連結 | 輕量級 |
密碼 + Authenticator App | TOTP(如 Google Authenticator) | Firebase 未原生支援,要另外串 |
Firebase 目前支援的 MFA 是「SMS 簡訊」或「Authenticator App (Beta)」,我們先從 最易上手的 SMS 版本 實作起。 |
流程是:使用者登入後 → 進入「帳號安全」頁 → 綁定手機成為第二道防線
import {
getAuth,
PhoneAuthProvider,
multiFactor,
RecaptchaVerifier,
} from "firebase/auth";
export const enrollMFAWithSMS = async (phoneNumber) => {
const auth = getAuth();
const user = auth.currentUser;
if (!user) throw new Error("尚未登入");
// 建立 reCAPTCHA 驗證器
const appVerifier = new RecaptchaVerifier("mfa-recaptcha-container", {}, auth);
// 使用 PhoneAuthProvider 發送驗證碼
const provider = new PhoneAuthProvider(auth);
const verificationId = await provider.verifyPhoneNumber(phoneNumber, appVerifier);
// 提醒前端等待使用者輸入簡訊驗證碼
return verificationId; // 讓前端儲存,用於 verify
};
import { PhoneAuthProvider, multiFactor } from "firebase/auth";
export const verifyMFAEnrollment = async (verificationId, smsCode) => {
const auth = getAuth();
const user = auth.currentUser;
if (!user) throw new Error("尚未登入");
const cred = PhoneAuthProvider.credential(verificationId, smsCode);
const multiFactorUser = multiFactor(user);
await multiFactorUser.enroll(cred, "My Phone Number");
console.log("已成功綁定 MFA");
};
當使用者下一次登入時,Firebase 會自動丟出 錯誤 auth/multi-factor-auth-required
,此時你必須再次發簡訊驗證碼來完成登入。
import {
signInWithEmailAndPassword,
PhoneAuthProvider,
RecaptchaVerifier,
} from "firebase/auth";
export const signInWithMFA = async (email, password) => {
const auth = getAuth();
try {
return await signInWithEmailAndPassword(auth, email, password);
} catch (error) {
if (error.code === "auth/multi-factor-auth-required") {
const resolver = error.resolver; // 必須儲存,之後用來完成驗證
const phoneInfoOptions = resolver.hints[0]; // 預設取第一個綁定的電話
// 建立 reCAPTCHA 驗證器
const appVerifier = new RecaptchaVerifier("mfa-recaptcha-container", {}, auth);
const provider = new PhoneAuthProvider(auth);
const verificationId = await provider.verifyPhoneNumber(phoneInfoOptions, appVerifier);
// 回傳資訊,等待使用者輸入 code
return { resolver, verificationId };
} else {
throw error;
}
}
};
// 完成登入的第二階段
export const completeMFASignIn = async (resolver, verificationId, smsCode) => {
const cred = PhoneAuthProvider.credential(verificationId, smsCode);
const userCredential = await resolver.resolveSignIn(cred);
console.log("登入成功", userCredential);
return userCredential;
};